﻿using System;
using System.Collections.Generic;
using System.IO;

namespace FirstFloor.Documents.Pdf.Fonts
{
    /// <summary>
    /// A composite font, a font composed of glyphs from a descendant CIDFont.
    /// </summary>
    internal class Type0Font
        : FontBase
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="Type0Font"/> class.
        /// </summary>
        /// <param name="fontDictionary">The font dictionary.</param>
        public Type0Font(DictionaryObject fontDictionary)
            : base(fontDictionary)
        {
            var descendantFonts = fontDictionary.Get<ArrayObject>("DescendantFonts");
            this.CIDFontDictionary = descendantFonts.Get<DictionaryObject>(0);
            this.CIDFontType = this.CIDFontDictionary.Get<string>("Subtype");
            this.FontName = this.CIDFontDictionary.Get<string>("BaseFont");

            if (this.CIDFontType == "CIDFontType2") {
                if (this.FontName.IndexOf('+') == 6) {
                    this.FontName = this.FontName.Substring(7);
                    this.IsSubset = true;
                }
            }

            this.FontDescriptor = this.CIDFontDictionary.Get<DictionaryObject>("FontDescriptor");
            if (this.FontDescriptor == null) {
                throw new PdfParseException(Resources.MissingFontDescriptor);
            }
        }
        /// <summary>
        /// Gets the CID font dictionary.
        /// </summary>
        /// <value>The CID font dictionary.</value>
        public DictionaryObject CIDFontDictionary { get; private set; }
        /// <summary>
        /// Gets the type of the CID font.
        /// </summary>
        /// <value>The type of the CID font.</value>
        public string CIDFontType { get; private set; }

        /// <summary>
        /// Gets the font file.
        /// </summary>
        /// <returns></returns>
        public override Stream GetFontFile()
        {
            if (this.CIDFontType == "CIDFontType2") {
                var fontFile2 = this.FontDescriptor.Get<DictionaryObject>("FontFile2");
                var stream = fontFile2.Owner.ParseStreamObject(fontFile2);
                return stream.OpenFilteredStream();
            }

            return null;
        }

        /// <summary>
        /// Parses the Glyph indices from specified string value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        protected override IEnumerable<GlyphIndexPoint> ParseIndices(string value)
        {
            // 1:1 convert string to byte array
            var b = new byte[value.Length];
            for (int i = 0; i < b.Length; i++) {
                b[i] = (byte)value[i];
            }
            return ParseIndices(b);
        }

        /// <summary>
        /// Parses the Glyph indices from specified string value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        protected override IEnumerable<GlyphIndexPoint> ParseIndices(byte[] value)
        {
            var encoding = this.FontDictionary["Encoding"] as string;
            var cidToGIDMap = this.CIDFontDictionary.Get<string>("CIDToGIDMap", "Identity");
            if (this.CIDFontType == "CIDFontType2" && encoding == "Identity-H" && cidToGIDMap == "Identity") {
                var dw = this.CIDFontDictionary.Get<int>("DW", 1000);
                var w = this.CIDFontDictionary.Get<ArrayObject>("W", null);

                for (var i = 0; i < value.Length / 2; i++) {
                    // see PDF Reference 1.7, page 445 
                    var cid = (ushort)(value[2 * i] * 0x100 + value[2 * i + 1]);

                    yield return new GlyphIndexPoint() {
                        GlyphIndex = cid,
                        //UnicodeCodePoint = (char)cid, 
                        Width = CalculateWidth(cid, w, dw),
                        IsSpaceCharacter = false    // word spacing only applied for single-byte character code
                    };
                }
            }
        }

        private static int CalculateWidth(int cid, ArrayObject w, int dw)
        {
            // see PDF Reference 1.7, page 439 
            if (w != null) {
                var i = 0;
                while (i < w.Count) {
                    var cfirst = w.Get<int>(i);
                    if (w[i + 1] is double) {
                        // all cids in range have same width
                        var clast = (double)w[i + 1];
                        if (cid >= cfirst && cid <= clast) {
                            return w.Get<int>(i + 2);
                        }
                        i += 3;
                    }
                    else {
                        var widths = w.Get<ArrayObject>(i + 1);
                        if (cid >= cfirst && cid < cfirst + widths.Count) {
                            return widths.Get<int>(cid - cfirst);
                        }

                        i += 2;
                    }
                }
            }
            return dw;
        }
    }
}
